
#
# a request abstraction - this is from 'camping' mostly
#

class BaseRequest

  attr_accessor :status         # http status
  attr_accessor :method         # a copy of the http method 'GET' or 'POST'
  attr_accessor :cookies        # the cookies ( from header )
  attr_accessor :params         # params
  attr_accessor :referer        # the referer (from header )
  attr_accessor :content_type   # as in 'text/plain'
  attr_accessor :env

  attr_accessor :host           # host as in site.com
  attr_accessor :path           # path as in /login
  attr_accessor :request_uri    # object that holds .host and .path
  attr_accessor :headers_out    # write these

  ##########################################################################
  # slightly modified hash; from camping - probably obsolete now
  ##########################################################################

  #
  # hash with indifferent access from ruby; with some local changes
  # 
  H = HashWithIndifferentAccess
  class H
    def method_missing(m,*a)
      if m.to_s =~ /=$/
        self[$`] = a[0]
      elsif a.empty?
        self[m]
      else
        raise NoMethodError, "#{m}"
      end
    end
    alias_method :u, :regular_update
  end

  def []=(a,b)
    @headers_out = H.new if !@headers_out
    @headers_out[a]=b
  end

  def [](a) 
    @headers_out = H.new if !@headers_out
    @headers_out[a]
  end

  ###################################################################
  # escape
  ###################################################################

  # URL escapes a string.
  #
  #   escape("I'd go to the museum straightway!")  
  #     #=> "I%27d+go+to+the+museum+straightway%21"
  #
  def escape(s); s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n){'%'+$1.unpack('H2'*$1.size).join('%').upcase}.tr(' ', '+') end

  #####################################################################
  # unescape
  #####################################################################

  # Unescapes a URL-encoded string.
  #
  #   un("I%27d+go+to+the+museum+straightway%21") 
  #     #=> "I'd go to the museum straightway!"
  #
  def unescape(s); s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1.delete('%')].pack('H*')} end

  #####################################################################
  # parsing cookies and the like
  #####################################################################

  # Parses a query string into an H object.
  #
  #   params = qs_parse("name=Philarp+Tremain&hair=sandy+blonde")
  #   params.name
  #     #=> "Philarp Tremaine"
  #
  # Also parses out the Hash-like syntax used in PHP and Rails and builds
  # nested hashes from it.
  #
  #   params = qs_parse("post[id]=1&post[user]=_why")
  #     #=> {'post' => {'id' => '1', 'user' => '_why'}}
  #
  def qs_parse(qs, d = '&;')
    m = proc { |_,o,n| o.u(n,&m) rescue([*o]<<n) }
    (qs||'').split(/[#{d}] */n).inject(H[]) { |h,p|
           if !p || p.length < 1 then p = " " end;
              k, v=unescape(p||" ").split('=',2)
              h.u(k.split(/[\]\[]+/).reverse.
                  inject(v) { |x,i| H[i,x] },&m)
          }
  end

  # Parses a string of cookies from the <tt>Cookie</tt> header.
  def kp(s)
    return qs_parse(s, ';,')
  end

  #
  # parse inbound arguments; builds a temporary file of post args
  # could use cgi...
  #
  def parse(r,e)
    @status = 200
    @method = (e['REQUEST_METHOD']||'GET').downcase
    @env = e
    @root = e['SCRIPT_NAME'].sub(/\/$/,'') if e['SCRIPT_NAME']
    @k = kp(e['HTTP_COOKIE'])
    @cookies = @k.dup
    qs = qs_parse(e['QUERY_STRING'])
    @in = r
    if %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)|n.match(e['CONTENT_TYPE'])
      b = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/
      until @in.eof?
        fh=H[]
        for l in @in
          case l
          when "\r\n": break
          when /^Content-Disposition: form-data;/
            fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten]
          when /^Content-Type: (.+?)(\r$|\Z)/m
            fh[:type] = $1
          end
        end
        fn=fh[:name]
        o=if fh[:filename]
          o=fh[:tempfile]=Tempfile.new(:C)
          o.binmode
        else
          fh=""
        end
        while l=@in.read(16384)
          if l=~b
            o<<$`.chomp  # this line makes radrails sad
            @in.seek(-$'.size,IO::SEEK_CUR)
            break
          end
          o<<l
       end
        qs[fn]=fh if fn
        fh[:tempfile].rewind if fh.is_a?H
      end
    elsif @method == "post" && @in
      qs.merge!(qs_parse(@in.read))
    end
    @params = qs.dup
  end

end
